//
// Created by tcj on 2022/2/24.
//
//协程调度
#include "scheduler.h"
#include "log.h"
#include "macro.h"
#include "hook.h"


namespace sylar{
    static sylar::Logger::ptr g_logger = SYLAR_LOG_NAME("system");

//有且只有thread_local关键字修饰的变量具有线程周期(thread duration)，
//          这些变量(或者说对象）在线程开始的时候被生成(allocated)，
//              在线程结束的时候被销毁(deallocated)
//每一个线程都有自己的独立变量实例
//当前协程调度器
    static thread_local Scheduler* t_scheduler = nullptr;
//    当前协程调度器调度的协程
    static thread_local Fiber* t_scheduler_fiber = nullptr;

    /*
      * 构造函数
      * threads:线程数量
      * use_caller:是否使用当前线程调用协程
      * name:协程调度器名称
      *
      * */

//    use_caller:表示是否使用当前线程来进行协程的调度
    Scheduler::Scheduler(size_t threads, bool use_caller,const std::string& name)
        :m_name(name){
        SYLAR_ASSERT(threads>0);

        if(use_caller){
            // 线程中的一个主协程进行创建
            sylar::Fiber::GetThis();
            --threads;


            SYLAR_ASSERT(GetThis() == nullptr);
            t_scheduler = this;

//           就是生成一个协程，执行函数run
            m_rootFiber.reset(new Fiber
                        (std::bind(&Scheduler::run,this),
                                    0,true));

//            SYLAR_LOG_DEBUG(g_logger)<<"set_name: "<<m_name;
            sylar::Thread::SetName(m_name);


            t_scheduler_fiber = m_rootFiber.get();
            m_rootThread = sylar::GetThreadId();
            m_threadIds.push_back(m_rootThread);
//            SYLAR_LOG_DEBUG(g_logger)<<"over....";
        }else{
            m_rootThread =-1;
        }
        m_threadCount = threads;
    }

    Scheduler::~Scheduler() {
        SYLAR_ASSERT(m_stopping);
        if(GetThis() == this){
            t_scheduler = nullptr;
        }
    }

    Scheduler* Scheduler::GetThis() {
        return t_scheduler;
    }

    Fiber *Scheduler::GetMinFiber() {
        return t_scheduler_fiber;
    }

//  启动协程调度器--创建一个线程池
    void Scheduler::start(){
        MutexType::Lock  lock(m_mutex);
        if(!m_stopping){
            return;
        }
        m_stopping = false;
        SYLAR_ASSERT(m_threads.empty());

        m_threads.resize(m_threadCount);
        for(size_t i=0;i<m_threadCount;++i){
            m_threads[i].reset(new Thread(std::bind(&Scheduler::run,this),m_name+"_"+std::to_string(i)));
            m_threadIds.push_back(m_threads[i]->getid());
        }
        lock.unlock();

    }

    //停止协程调度器
    /* 功能：停止scheduler调度器运行，分情况讨论
     * 流程简介：
     *      1。只有一个线程（即主线程/调度协程在运行），并且调度协程处于终止态或者创建态，直接调用stopping（）进行回收
     *      2。有多个线程（一组线程池），先设置标志位m_stopping，唤醒tickle()其他子线程根据该标志位退出；都完毕之后，再将调度协程唤醒退出
     *
     * */
    void Scheduler::stop(){
        m_autoStop = true; // 可以使得调度器再调度完所有任务后自动退出
        if(m_rootFiber && m_threadCount==0 &&
                (m_rootFiber->getState() == Fiber::TERM
                    || m_rootFiber->getState() == Fiber::INIT)){
//            SYLAR_LOG_INFO(g_logger) <<this<<" stopped";
            m_stopping =true;
            if(stopping()){
               return;
            }
        }

//        bool exit_on_fiber = false;
        if(m_rootThread !=-1){
            SYLAR_ASSERT(GetThis() ==this);
        }else{
            SYLAR_ASSERT(GetThis()!=this);
        }
//        SYLAR_LOG_INFO(g_logger)<<"daio";
        m_stopping = true;
        for(size_t i=0;i<m_threadCount;++i){
            SYLAR_LOG_INFO(g_logger)<<"tickle i="<<i<<" dswfgsuigofwg";
            // 将所有创建的线程唤醒，让他们自己退出
            tickle();
        }

        if(m_rootFiber){
            tickle();
        }

        if(m_rootFiber){
            if(!stopping()){
                m_rootFiber->call();
            }
        }
        std::vector<Thread::ptr> thrs;
        {
            MutexType::Lock lock(m_mutex);
            thrs.swap(m_threads);
        }
        for(auto& i: thrs){
            i->join();
        }

    }

    void Scheduler::SetThis() {
        t_scheduler = this;
    }

//    协程调度函数
    void Scheduler::run() {
//        SYLAR_LOG_DEBUG(g_logger) <<m_name << " run";
//        Fiber::GetThis();
        set_hook_enable(true);
        SetThis();
//        这个就是如果不使用uese_caller（不使用main线程进行进行协程调度）的时候创建一个
        if(sylar::GetThreadId() != m_rootThread){
            t_scheduler_fiber = Fiber::GetThis().get();
        }
        Fiber::ptr idle_fiber(new Fiber(std::bind(&Scheduler::idle,this)));
        Fiber::ptr cb_fiber;

        FIberAndThread ft;

        while(true){
            ft.reset();
            bool tickle_me = false;
            bool is_active= false;
            {
                MutexType::Lock lock(m_mutex);
                auto it = m_fibers.begin();
                while(it!=m_fibers.end()){
                    if(it->thread != -1 && it->thread != sylar::GetThreadId()){
                        // 指定了调度线程，但是不是在当前线程上进行调度，标记以下需要通知
                        // 其他线程进行调度，然后跳过这个任务，继续下一个
                        ++it;
                        tickle_me = true;
                        continue;
                    }
                    SYLAR_ASSERT(it->fiber || it->cb);
                    if(it->fiber && it->fiber->getState() == Fiber::EXEC){
                        // 任务队列的协程一定是正在运行的话，就直接跳到下一个
                        ++it;
                        continue;
                    }

                    ft = *it;
//                    tickle_me = true;
                    m_fibers.erase(it);
                    ++m_activeThreadCount;
                    is_active=true;
                    break;
                }
//              当前线程拿完一个任务，发现任务队列还有剩余，那么tickle一下其他线程。
                tickle_me |= it != m_fibers.end();
            }
            if(tickle_me){
                // 轮询任务队列时候，发现有任务不属于自己执行，并且也没有指定任何线程执行的时候要调用tickle()取唤醒线程执行这些任务
                tickle();
            }


            if((ft.fiber) && (ft.fiber->getState()!=Fiber::TERM
                    && ft.fiber->getState() != Fiber::EXCEPT)){
                // resume协程，resume返回的时候，协程要么执行完了，要么本路yeild了。总之这任务完成了，活跃线程-1
                ft.fiber->swapIn();
                --m_activeThreadCount;

                if(ft.fiber->getState() == Fiber::READY){
                    schedule(ft.fiber);
                }else if(ft.fiber->getState() != Fiber::TERM
                        && ft.fiber->getState()!=Fiber::EXCEPT){
                    ft.fiber->m_state = Fiber::HOLD;
                }
                ft.reset();
            }else if(ft.cb){
                if(cb_fiber){
                    cb_fiber->reset(ft.cb);
                }else{
                    cb_fiber.reset(new Fiber(ft.cb));
                }
                ft.reset();
                cb_fiber->swapIn();
                --m_activeThreadCount;

                if(cb_fiber->getState() == Fiber::READY){
                    schedule(cb_fiber);
                    cb_fiber.reset();
                }else if(cb_fiber->getState() == Fiber::EXCEPT
                        || cb_fiber->getState() == Fiber::TERM){
                    cb_fiber->reset(nullptr);
                }else{ //if(cb_fiber->getState() !=Fiber::TERM){
                    cb_fiber->m_state = Fiber::HOLD;
                    cb_fiber.reset();
                }
            }else{
                // 进入这里，说明任务队列空了
                if(is_active){
                    --m_activeThreadCount;
                    continue;
                }
                if(idle_fiber->getState() == Fiber::TERM){
                    // 如果调度器没有调度任务的时候，idle协程会不断的resume/yeild，不会结束，如果idle协程结束了，那么一定是调度器结束了
                    SYLAR_LOG_INFO(g_logger)<<"idle fiber term";
                    break;
                }
                ++m_idleThreadCount;
                idle_fiber->swapIn();
                --m_idleThreadCount;
                if(idle_fiber->getState() != Fiber::TERM
                        && idle_fiber->getState() != Fiber::EXCEPT){
                    idle_fiber->m_state = Fiber::HOLD;
                }
            }

        }
    }

    // 通知有新任务来了
    void Scheduler::tickle() {
        SYLAR_LOG_INFO(g_logger)<<"tickle";
    }

//    对于stopping，iomanager再判断是否可退出时，还要加上所有io事件都完成调度的条件
    bool Scheduler::stopping() {
        MutexType::Lock lock(m_mutex);
        return m_autoStop && m_stopping && m_fibers.empty() &&
                m_activeThreadCount==0;
    }

    void Scheduler::idle() {
//        SYLAR_LOG_INFO(g_logger)<<"idle";
        while(!stopping()){
            sylar::Fiber::YieldToHold();
        }
    }
}